package main

import (
	"bufio"
	"bytes"
	"crypto/rand"
	"fmt"
	"math/big"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"sync"
	"text/template"
	"time"
	"unsafe"

	"golang.org/x/sys/windows"
)

const (
	mysqlVersion        = "mysql-8.0.28-winx64.zip"
	mysqlVerionFileSize = 222021254
	mysqlRootDir        = "mysql8.0"
	dectaskServiceName  = "Dectask"
	mysqlServiceName    = "DectaskMysql8"
	redisServiceName    = "DectaskRedis"
	logFileName         = "install.log"
)

var mysqlDownloadUrls = []string{
	"https://mirrors.aliyun.com/mysql/MySQL-8.0/" + mysqlVersion,
	"https://dev.mysql.com/get/Downloads/MySQL-8.0/" + mysqlVersion,
	"http://download.dectask.com/mysql/" + mysqlVersion,
}

var (
	rootPassword     = ""
	dectaskPort      = "33036"
	dectaskUser      = "dectask_user"
	dectaskPass      = ""
	dectaskPath      = "localhost:" + dectaskPort
	dectaskDB        = "dectask"
	dectaskRedisPort = "63790"
	logFile          *os.File
	logMutex         sync.Mutex
)

func init() {
	rootPassword = generateRandomPassword(12)
	dectaskPass = generateRandomPassword(12)
}

// MySQLConfig 用于模板替换的结构体
type MySQLConfig struct {
	Basedir string
	Socket  string
}

func main() {
	// 初始化日志文件
	if err := initLogFile(); err != nil {
		fmt.Printf("无法初始化日志文件: %v\n", err)
		return
	}
	defer logFile.Close()

	// 检查管理员权限
	if !isAdmin() {
		logError("需要管理员权限运行")
		// runAsAdmin()
		// return
	}
	logMessage("=== Dectask 自动安装程序 ===")

	// 获取当前工作目录
	rootDir, err := os.Getwd()
	if err != nil {
		logError("获取当前目录失败: %v", err)
		os.Exit(1)
	}
	// 如果install.lock存在，则提示不能安装，先手动删除
	dectaskDir := filepath.Join(rootDir, "dectask")
	lockFile := filepath.Join(dectaskDir, "install.lock")
	if _, err = os.Stat(lockFile); !os.IsNotExist(err) {
		logError("dectask/install.lock 文件存在，请先手动删除。注意：重新安装将会导致数据库删除和重建！")
		os.Exit(1)
	}
	// 切换到工作目录
	if err := os.Chdir(rootDir); err != nil {
		logError("切换到工作目录失败: %v", err)
		os.Exit(1)
	}

	// 先下载mysql
	downloadMysql()

	// 1. 安装 MySQL
	mysqlDir := filepath.Join(rootDir, mysqlRootDir)
	mysqlDir = strings.ReplaceAll(mysqlDir, "\\", "/")
	if err := installMySQL(mysqlDir); err != nil {
		logError("安装 MySQL 失败: %v", err)
		os.Exit(1)
	}

	// 2. 安装 Redis
	redisDir := filepath.Join(rootDir, "redis")
	redisDir = strings.ReplaceAll(redisDir, "\\", "/")
	if err := installRedis(redisDir); err != nil {
		logError("安装 Redis 失败: %v", err)
		// os.Exit(1)
	}

	// 创建数据库和用户
	mysqlDir = filepath.Join(rootDir, "mysql8.0")
	if err := createDatabaseAndUser(mysqlDir); err != nil {
		logError("创建数据库和用户失败: %v", err)
	}
	logMessage("数据库和用户创建成功")
	// 3. 更新 dectask 配置文件
	if err := os.Chdir(dectaskDir); err != nil {
		logError("切换到dectask目录失败: %v", err)
		os.Exit(1)
	}
	if err := updateDectaskConfig(dectaskDir); err != nil {
		logError("更新 Dectask 配置失败: %v", err)
		os.Exit(1)
	}

	// 4. 执行 dectask 安装
	if err := installDectask(dectaskDir); err != nil {
		logError("安装 Dectask 失败: %v", err)
		os.Exit(1)
	}

	// 5. 安装 dectask 服务
	if err := installDectaskService(dectaskDir); err != nil {
		logError("%v", err)
	}

	if err := startService(dectaskServiceName); err != nil {
		logError("启动 "+dectaskServiceName+" 服务失败: %v", err)
	}

	logMessage("\n=== 安装完成 ===")
	logMessage("MySQL创建用户 root 密码:%s 和 dectask_user 密码:%s", rootPassword, dectaskPass)
	logDir := filepath.Join(dectaskDir, "log")
	accountFile := filepath.Join(logDir, "account.log")
	account, err := os.ReadFile(accountFile)
	if err != nil {
		logError("无法读取账号文件,安装可能已经失败: %v", err)
	} else {
		logMessage(string(account))
	}
}

func downloadMysql() {
	logMessage("\n=== 下载数据库 MySQL 8.0 ===")
	dirExist := false
	if _, err := os.Stat(mysqlRootDir); !os.IsNotExist(err) {
		dirExist = true
		logMessage(mysqlRootDir + "目录已经存在，无需下载")
	}
	fileExist := false
	// 判断当前目录下是否有mysql-8.0.28-winx64.zip文件,且文件大小正确
	if _, err := os.Stat("./" + mysqlVersion); !os.IsNotExist(err) {
		fileInfo, err := os.Stat("./" + mysqlVersion)
		if err != nil {
			logError("文件已经存在但获取文件大小失败: %v", err)
		} else {
			fileSize := fileInfo.Size()
			if fileSize == mysqlVerionFileSize {
				logError(mysqlVersion + "文件已经存在，无需下载")
				fileExist = true
			}
		}
	}
	if !fileExist && !dirExist {
		cfg := DownloadConfig{
			URLs:           mysqlDownloadUrls,
			OutputPath:     mysqlVersion,
			ChunkSize:      10 * 1024 * 1024, // 10MB分片
			MaxConcurrency: 8,                // 8并发
			Timeout:        15 * time.Minute,
			ProgressFunc: func(percent float64) {
				fmt.Printf("\r下载进度: %.2f%%", percent)
			},
		}
		err := DownloadWithFallback(cfg)
		if err != nil {
			fmt.Printf("\n下载失败: %v\n", err)
			return
		}
		fmt.Println("\n下载完成!")
	}
	// 解压文件
	if dirExist {
		logMessage(mysqlRootDir + " 文件夹已经存在无需解压缩!")
		return
	}
	_ = os.RemoveAll("./" + mysqlRootDir)
	if err := unzip("./"+mysqlVersion, "./"+mysqlRootDir); err != nil {
		fmt.Printf("解压失败: %v\n", err)
		return
	}
	// 将 mysql8.0.28 内的文件移动到 mysql8.0.28 目录
	if err := CopyFiles("./"+mysqlRootDir+"/mysql-8.0.28-winx64/*", "./"+mysqlRootDir); err != nil {
		fmt.Printf("移动文件失败: %v\n", err)
		return
	}
	_ = os.RemoveAll("./" + mysqlRootDir + "/mysql-8.0.28-winx64")
	logMessage("解压完成!")
}

func installMySQL(mysqlDir string) error {
	logMessage("\n=== 安装 MySQL 8.0 ===")

	// 检查 MySQL 服务是否已存在
	if serviceExists(mysqlServiceName) {
		logMessage("检测到已存在 %s 服务", mysqlServiceName)
		if !askForConfirmation("是否删除并重新安装 MySQL 服务?") {
			return fmt.Errorf("用户取消 MySQL 安装")
		}

		// 停止并删除现有服务
		if err := stopAndDeleteService(mysqlServiceName); err != nil {
			return fmt.Errorf("删除现有 MySQL 服务失败: %v", err)
		}
		// 删除 MySQL 数据目录
		if err := os.RemoveAll(filepath.Join(mysqlDir, "data")); err != nil {
		}
	}

	// 生成随机密码 (12字符)
	logMessage("已生成 MySQL root 密码和 dectask 用户密码")

	// 准备 MySQL 配置文件
	myIniPath := filepath.Join(mysqlDir, "my.ini")
	templatePath := "./my.ini.template"

	// 如果模板文件不存在，创建默认模板
	if _, err := os.Stat(templatePath); os.IsNotExist(err) {
		return fmt.Errorf("mysql模板文件不存在")
	}
	// 读取模板文件
	templateContent, err := os.ReadFile(templatePath)
	if err != nil {
		return fmt.Errorf("读取模板文件失败: %v", err)
	}
	// 解析模板
	tmpl, err := template.New("mysqlConfig").Parse(string(templateContent))
	if err != nil {
		return fmt.Errorf("解析模板失败: %v", err)
	}

	// 准备模板数据
	configData := MySQLConfig{
		Basedir: mysqlDir,
		Socket:  strings.ReplaceAll(filepath.Join(mysqlDir, "bin", "mysql.sock"), "\\", "/"),
	}

	// 执行模板替换
	var buf bytes.Buffer
	if err := tmpl.Execute(&buf, configData); err != nil {
		return fmt.Errorf("模板替换失败: %v", err)
	}

	// 写入最终配置文件
	if err := os.WriteFile(myIniPath, buf.Bytes(), 0o644); err != nil {
		return fmt.Errorf("创建 my.ini 失败: %v", err)
	}

	logMessage("已生成 MySQL 配置文件: %s", myIniPath)

	// 安装 MySQL 服务
	cmd := exec.Command(filepath.Join(mysqlDir, "bin", "mysqld.exe"),
		"--install", mysqlServiceName,
		"--defaults-file="+myIniPath)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	if err := cmd.Run(); err != nil {
		return fmt.Errorf("安装 MySQL 服务失败: %v", err)
	}
	logMessage("MySQL 服务安装成功")

	// 初始化 MySQL
	initCmd := exec.Command(filepath.Join(mysqlDir, "bin", "mysqld.exe"), "--initialize-insecure")
	initCmd.Stdout = os.Stdout
	initCmd.Stderr = os.Stderr
	if err := initCmd.Run(); err != nil {
		fmt.Errorf("MySQL 初始化失败: %v", err)
	}
	logMessage("MySQL 初始化成功")

	// 启动 MySQL 服务
	if err := startService(mysqlServiceName); err != nil {
		return fmt.Errorf("启动 MySQL 服务失败: %v", err)
	}
	logMessage("MySQL 服务启动成功")

	// 等待 MySQL 服务完全启动
	time.Sleep(5 * time.Second)

	// 创建数据库和用户
	if err := createDatabaseAndUser(mysqlDir); err != nil {
		return fmt.Errorf("创建数据库和用户失败: %v", err)
	}
	logMessage("数据库和用户创建成功")

	// 保存密码到文件
	if err := savePasswordsToFile(filepath.Dir(mysqlDir + "/../")); err != nil {
		return fmt.Errorf("保存密码文件失败: %v", err)
	}
	logMessage("密码已保存到文件")
	logMessage("MySQL 安装完成")
	return nil
}

func installRedis(redisDir string) error {
	logMessage("\n=== 安装 Redis 3.0 ===")

	// 检查 Redis 服务是否已存在
	if serviceExists(redisServiceName) {
		logMessage("检测到已存在 %s 服务", redisServiceName)
		if !askForConfirmation("是否删除并重新安装 Redis 服务?") {
			return fmt.Errorf("用户取消 Redis 安装")
		}

		// 停止并删除现有服务
		if err := stopAndDeleteService(redisServiceName); err != nil {
			return fmt.Errorf("删除现有 Redis 服务失败: %v", err)
		}
	}

	// 安装 Redis 服务
	confPath := filepath.Join(redisDir, "redis.windows.conf")
	cmd := exec.Command(filepath.Join(redisDir, "redis-server.exe"),
		"--service-install", confPath,
		"--service-name", redisServiceName)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	if err := cmd.Run(); err != nil {
		return fmt.Errorf("安装 Redis 服务失败: %v", err)
	}
	logMessage("Redis 服务安装成功")

	// 启动 Redis 服务
	if err := startService(redisServiceName); err != nil {
		return fmt.Errorf("启动 Redis 服务失败: %v", err)
	}
	logMessage("Redis 服务启动成功")

	logMessage("Redis 安装完成")
	return nil
}

func updateDectaskConfig(dectaskDir string) error {
	logMessage("\n=== 更新 Dectask 配置 ===")

	configTplPath := filepath.Join(dectaskDir, "config.tpl.yaml")
	config, err := os.ReadFile(configTplPath)
	if err != nil {
		return fmt.Errorf("读取 config.yaml 失败: %v", err)
	}

	// 更新 MySQL 配置
	configStr := string(config)
	configStr = strings.ReplaceAll(configStr, "{mysql.path}", dectaskPath)
	configStr = strings.ReplaceAll(configStr, "{mysql.dbname}", dectaskDB)
	configStr = strings.ReplaceAll(configStr, "{mysql.username}", dectaskUser)
	configStr = strings.ReplaceAll(configStr, "{mysql.password}", dectaskPass)

	// 更新 Redis 配置
	configStr = strings.ReplaceAll(configStr, "{redis.addr}", "127.0.0.1:"+dectaskRedisPort)
	configPath := filepath.Join(dectaskDir, "config.yaml")
	if err := os.WriteFile(configPath, []byte(configStr), 0o644); err != nil {
		return fmt.Errorf("写入 config.yaml 失败: %v", err)
	}

	logMessage("Dectask 配置更新完成")
	return nil
}

func installDectask(dectaskDir string) error {
	logMessage("\n=== 安装 Dectask ===")
	if err := os.Chdir(dectaskDir); err != nil {
		logError("切换到dectask目录失败: %v", err)
		os.Exit(1)
	}
	// configFile := filepath.Join(dectaskDir, "config.yaml")
	cmd := exec.Command(filepath.Join(dectaskDir, "dectask.exe"), "install", "zh_cn", "-y")
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Dir = dectaskDir
	if err := cmd.Run(); err != nil {
		return fmt.Errorf("执行 dectask 安装失败: %v", err)
	}

	logMessage("Dectask 安装完成")
	return nil
}

func installDectaskService(dectaskDir string) error {
	logMessage("\n=== 安装 Dectask 服务 ===")
	// sc create Dectask binPath= "\"D:\gopath\src\dectask\dectask.exe\" -c=\"D:\gopath\src\dectask\config.yaml\"" start=auto

	// 检查 MySQL 服务是否已存在
	if serviceExists(dectaskServiceName) {
		logMessage("检测到已存在 %s 服务", dectaskServiceName)
		if !askForConfirmation("是否删除并重新安装 " + dectaskServiceName + " 服务?") {
			return fmt.Errorf("用户取消 " + dectaskServiceName + " 安装")
		}
		// 停止并删除现有服务
		if err := stopAndDeleteService(dectaskServiceName); err != nil {
			return fmt.Errorf("删除现有 "+dectaskServiceName+" 服务失败: %v", err)
		}
		name := strings.ToLower(dectaskServiceName)
		cmd := exec.Command("taskkill", "/f", "/im", name+".exe")
		err := cmd.Run()
		if err != nil {
			fmt.Printf("taskkill failed: " + err.Error())
		}

	}

	binPath := fmt.Sprintf(`binPath="%s\dectask.exe" -c="%s\config.yaml"`, dectaskDir, dectaskDir)
	binPath = strings.ReplaceAll(binPath, "/", "\\")
	fmt.Println("sc create Dectask " + binPath + " start=auto")
	// binPath = `binPath="D:\gopath\src\install\dectask\dectask.exe" -c="D:\gopath\src\install\dectask\config.yaml"`
	cmd := exec.Command("sc", "create", dectaskServiceName, binPath, "start=auto")
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	err := cmd.Run()
	if err != nil {
		return fmt.Errorf("Dectask 服务安装失败:" + err.Error())
	}

	logMessage("Dectask 服务安装完成")
	return nil
}

func createDatabaseAndUser(mysqlDir string) error {
	// 使用 mysql 客户端执行 SQL 命令
	mysqlCmd := filepath.Join(mysqlDir, "bin", "mysql.exe")

	sql := `
    CREATE DATABASE IF NOT EXISTS %s CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
    ALTER USER 'root'@'localhost' IDENTIFIED BY '%s';
    CREATE USER IF NOT EXISTS '%s'@'localhost' IDENTIFIED BY '%s';
    GRANT ALL PRIVILEGES ON %s.* TO '%s'@'localhost';
    FLUSH PRIVILEGES;
`
	sql = fmt.Sprintf(sql, dectaskDB, rootPassword, dectaskUser, dectaskPass, dectaskDB, dectaskUser)
	logMessage(sql)

	// 首次连接 MySQL (因为我们是 --initialize-insecure 初始化的，root 用户没有密码)
	cmd := exec.Command(mysqlCmd, "-u", "root", "-P"+dectaskPort, "-e", sql)

	var stderr bytes.Buffer
	cmd.Stderr = &stderr
	if err := cmd.Run(); err != nil {
		return fmt.Errorf("执行 SQL 命令失败: %v, 错误输出: %s", err, stderr.String())
	}

	return nil
}

func savePasswordsToFile(rootDir string) error {
	content := fmt.Sprintf(`MySQL 密码信息:
Root 用户: root
Root 密码: %s
Dectask 数据库: %s
Dectask 用户: %s
Dectask 密码: %s
`, rootPassword, dectaskDB, dectaskUser, dectaskPass)

	filePath := filepath.Join(rootDir, "mysql-password.log")
	logMessage("正在保存密码信息到文件: %s", filePath)
	if err := os.WriteFile(filePath, []byte(content), 0o644); err != nil {
		return err
	}

	return nil
}

func serviceExists(name string) bool {
	cmd := exec.Command("sc", "query", name)
	if err := cmd.Run(); err != nil {
		return false
	}
	return true
}

func stopAndDeleteService(name string) error {
	// 停止服务
	stopCmd := exec.Command("net", "stop", name)
	stopCmd.Stdout = os.Stdout
	stopCmd.Stderr = os.Stderr
	_ = stopCmd.Run() // 忽略错误，因为服务可能已经停止

	// 删除服务
	deleteCmd := exec.Command("sc", "delete", name)
	deleteCmd.Stdout = os.Stdout
	deleteCmd.Stderr = os.Stderr
	if err := deleteCmd.Run(); err != nil {
		return err
	}

	// 等待服务被删除
	time.Sleep(2 * time.Second)
	return nil
}

func startService(name string) error {
	cmd := exec.Command("net", "start", name)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	return cmd.Run()
}

func generateRandomPassword(length int) string {
	const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
	b := make([]byte, length)
	for i := range b {
		n, err := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
		if err != nil {
			panic(err)
		}
		b[i] = charset[n.Int64()]
	}
	return string(b)
}

func askForConfirmation(prompt string) bool {
	reader := bufio.NewReader(os.Stdin)
	for {
		fmt.Printf("%s [y/n]: ", prompt)
		input, err := reader.ReadString('\n')
		if err != nil {
			return false
		}
		input = strings.TrimSpace(strings.ToLower(input))
		if input == "y" || input == "yes" {
			return true
		} else if input == "n" || input == "no" {
			return false
		}
	}
}

func isAdmin() bool {
	_, err := os.Open("\\\\.\\PHYSICALDRIVE0")
	return err == nil
}

func runAsAdmin() {
	shell32 := windows.NewLazySystemDLL("shell32.dll")
	shellExecute := shell32.NewProc("ShellExecuteW")

	verb := "runas"
	exe, _ := os.Executable()
	cwd, _ := os.Getwd()
	args := strings.Join(os.Args[1:], " ")

	verbPtr, _ := windows.UTF16PtrFromString(verb)
	exePtr, _ := windows.UTF16PtrFromString(exe)
	cwdPtr, _ := windows.UTF16PtrFromString(cwd)
	argPtr, _ := windows.UTF16PtrFromString(args)

	ret, _, err := shellExecute.Call(
		uintptr(0),
		uintptr(unsafe.Pointer(verbPtr)),
		uintptr(unsafe.Pointer(exePtr)),
		uintptr(unsafe.Pointer(argPtr)),
		uintptr(unsafe.Pointer(cwdPtr)),
		uintptr(windows.SW_SHOWNORMAL),
	)

	if ret <= 32 {
		logError("无法以管理员身份运行: 错误代码 %d, %v", ret, err)
		os.Exit(1)
	}
	os.Exit(0)
}

func initLogFile() error {
	var err error
	logFile, err = os.OpenFile(logFileName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
	if err != nil {
		return fmt.Errorf("无法创建日志文件: %v", err)
	}
	return nil
}

func logMessage(format string, args ...interface{}) {
	msg := fmt.Sprintf(format, args...)
	timestamp := time.Now().Format("2006-01-02 15:04:05")
	logLine := fmt.Sprintf("[%s] %s\n", timestamp, msg)

	logMutex.Lock()
	defer logMutex.Unlock()

	fmt.Print(msg + "\n")
	logFile.WriteString(logLine)
}

func logError(format string, args ...interface{}) {
	msg := fmt.Sprintf(format, args...)
	timestamp := time.Now().Format("2006-01-02 15:04:05")
	logLine := fmt.Sprintf("[%s] [ERROR] %s\n", timestamp, msg)

	logMutex.Lock()
	defer logMutex.Unlock()

	fmt.Fprintf(os.Stderr, "错误: "+msg+"\n")
	logFile.WriteString(logLine)
}
